package edu.tongji.structureplayer.core
{
    import edu.tongji.structureplayer.core.math.Determinant;
    import edu.tongji.structureplayer.core.math.Matrix;
    import edu.tongji.structureplayer.structure.Constrain;
    import edu.tongji.structureplayer.structure.Element;
    import edu.tongji.structureplayer.structure.Load;
    import edu.tongji.structureplayer.structure.Node;
    import edu.tongji.structureplayer.structure.WellBehavedStructure;
    
    import org.as3commons.collections.Map;
    import org.as3commons.collections.framework.ILinkedListIterator;
    import org.as3commons.collections.framework.IMap;
    
    public class Solver implements SolverI
    {
        private var _structure:WellBehavedStructure;
        
        private var nodeArray:Array;
        private var elementArray:Array;
        private var nodeIDMap:IMap;
        
        private var localKArray:Array;
        private var tArray:Array;
        private var globalKArray:Array;
        private var kMatrix:Matrix;
        private var globalForce:Determinant;
        private var globalDelta:Determinant;
        
        public function solve(structure:WellBehavedStructure):void
        {
            _structure = structure;
            var validator:StructureValidator = new StructureValidator(_structure);
            validator.validate();
            
            nodeArray = _structure.nodes.toArray();
            elementArray = _structure.elements.toArray();
            buildNodeIDMap();
            
            if (!validator.isSemiRigid())
            {
                buildLocalKArray();
                buildTArray();
                buildGlobalKArray();
                buildKMatrix();
                buildGlobalForce();
                buildGlobalDelta();
            }
        }
        
        private function buildNodeIDMap():void
        {
            nodeIDMap = new Map();
            for (var i:uint=0; i<nodeArray.length; i++)
                nodeIDMap.add(nodeArray[i] as Node, i);
        }
        
        private function buildLocalKArray():void
        {
            localKArray = new Array(elementArray.length);
            for (var i:uint=0; i<elementArray.length; i++)
            {
                var e:Element = elementArray[i] as Element;
                var E:Number = e.property.materialProperty.elasticModulus;
                var A:Number = e.property.sectionConstant.crossArea;
                var I:Number = e.property.sectionConstant.secondMomentOfArea;
                var length:Number = Math.sqrt(
                    Math.pow( (e.startNode.x - e.endNode.x), 2 ) +
                    Math.pow( (e.startNode.y - e.endNode.y), 2 )
                );
                var EAdl:Number = E*A/length;
                var EIdl:Number = E*I/length;
                var EIdl2:Number = EIdl/length;
                var EIdl3:Number = EIdl2/length;
                
                var m:Matrix = new Matrix(6);
                
                m.data[0][0] = EAdl;
                m.data[3][3] = EAdl;
                m.data[0][3] = -EAdl;
                m.data[3][0] = -EAdl;
                
                m.data[1][1] = 12*EIdl3;
                m.data[4][4] = 12*EIdl3;
                m.data[1][4] = -12*EIdl3;
                m.data[4][1] = -12*EIdl3;
                
                m.data[1][2] = 6*EIdl2;
                m.data[1][2] = 6*EIdl2;
                m.data[1][5] = 6*EIdl2;
                m.data[5][1] = 6*EIdl2;
                m.data[4][2] = -6*EIdl2;
                m.data[2][4] = -6*EIdl2;
                m.data[4][5] = -6*EIdl2;
                m.data[5][4] = -6*EIdl2;
                
                m.data[2][2] = 4*EIdl;
                m.data[5][5] = 4*EIdl;
                
                m.data[5][2] = 2*EIdl;
                m.data[2][5] = 2*EIdl;
                
                localKArray[i] = m;
                
                /*(localKArray[i] as Matrix).debugPrint();
                trace();*/
            }
        }
        
        private function buildTArray():void
        {
            tArray = new Array(elementArray.length);
            for (var i:uint=0; i<elementArray.length; i++)
            {
                var e:Element = elementArray[i] as Element;
                
                var length:Number = Math.sqrt(
                    Math.pow( (e.startNode.x - e.endNode.x), 2 ) +
                    Math.pow( (e.startNode.y - e.endNode.y), 2 )
                );
                var sinAlpha:Number = (e.endNode.y - e.startNode.y) / length;
                var alpha:Number = Math.asin( sinAlpha );
                var cosAlpha:Number = Math.cos(alpha);
                
                var m:Matrix = new Matrix(6);
                m.data[0][0] = cosAlpha;
                m.data[1][1] = cosAlpha;
                m.data[3][3] = cosAlpha;
                m.data[4][4] = cosAlpha;
                
                m.data[0][1] = sinAlpha;
                m.data[3][4] = sinAlpha;
                m.data[1][0] = -sinAlpha;
                m.data[4][3] = -sinAlpha;
                
                m.data[2][2] = 1;
                m.data[5][5] = 1;
                
                tArray[i] = m;
                
                /*(tArray[i] as Matrix).debugPrint();
                trace();*/
            }
        }
        
        private function buildGlobalKArray():void
        {
            globalKArray = new Array(elementArray.length);
            for (var i:uint=0; i<elementArray.length; i++)
            {
                globalKArray[i] = Matrix.buildFromDeterminant(
                    (tArray[i] as Matrix).transpose()
                    .multiply(localKArray[i] as Matrix)
                    .multiply(tArray[i] as Matrix)
                );
                /*var t1:Determinant = (tArray[i] as Matrix).transpose();
                t1.debugPrint();
                var t2:Determinant = t1.multiply(localKArray[i] as Matrix);
                t2.debugPrint();
                var t3:Determinant = t2.multiply(tArray[i] as Matrix);
                t3.debugPrint();
                
                globalKArray[i] = Matrix.buildFromDeterminant(t3);*/
                /*(globalKArray[i] as Matrix).debugPrint();
                trace();*/
            }
        }
        
        private function buildKMatrix():void
        {
            
            kMatrix = new Matrix(nodeArray.length*3);
            var matrixArray:Array = kMatrix.spiltEqualy(nodeArray.length);
            
            for (var j:uint=0; j<elementArray.length; j++)
            {
                var e:Element = elementArray[j] as Element;
                var temp:Array = (globalKArray[j] as Matrix).spiltEqualy(2);
                var startIndex:uint = nodeIDMap.itemFor(e.startNode);
                var endIndex:uint = nodeIDMap.itemFor(e.endNode);
                (matrixArray[startIndex][startIndex]).addSelf(temp[0][0]);
                (matrixArray[startIndex][endIndex]).addSelf(temp[0][1]);
                (matrixArray[endIndex][startIndex]).addSelf(temp[1][0]);
                (matrixArray[endIndex][endIndex]).addSelf(temp[1][1]);
            }
            kMatrix.build(matrixArray);
            
            // This will cross some lines in the K Matrix according to constrains
            // on the Nodes.
            buildConstrain();
            
            kMatrix.debugPrint();
            trace();
        }
        
        private function buildGlobalForce():void
        {
            globalForce = new Determinant(nodeArray.length*3, 1);
            var it:ILinkedListIterator = 
                _structure.loads.iterator() as ILinkedListIterator;
            while (it.hasNext())
            {
                it.next();
                var load:Load = it.current as Load;
                if (load.getForce().length==3)
                {
                    var base:uint = uint(nodeIDMap.itemFor(load.node))*3;
                    for (var i:uint=0; i<3; i++)
                        globalForce.data[base+i][0] += 
                            load.getForce()[i];
                }
                else if (load.getForce().length == 6)
                {
                    var baseS:uint = uint(nodeIDMap.itemFor(load.element.startNode));
                    var baseE:uint = uint(nodeIDMap.itemFor(load.element.endNode));
                    for (var j:uint=0; j<3; j++)
                        globalForce.data[baseS+j][0] += 
                            load.getForce()[j];
                    for (var k:uint=0; k<3; k++)
                        globalForce.data[baseE+k][0] += 
                            load.getForce()[k+3];
                }
            }
        }
        
        private function buildConstrain():void
        {
            var it:ILinkedListIterator = 
                _structure.constrains.iterator() as ILinkedListIterator;
            while (it.hasNext())
            {
                it.next();
                var constrain:Constrain = it.current as Constrain;
                var base:uint = uint(nodeIDMap.itemFor(constrain.node))*3;
                
                // Special case for (true, true, false).
                // Regard it as (true, true, true)
                if (constrain.connection.xEnable 
                    && constrain.connection.yEnable 
                    && !constrain.connection.thetaEnable)
                    continue;
                
                if (!constrain.connection.xEnable)
                    kMatrix.setToZero(base);
                if (!constrain.connection.yEnable)
                    kMatrix.setToZero(base+1);
                if (!constrain.connection.thetaEnable)
                    kMatrix.setToZero(base+2);
            }
        }
        
        private function buildGlobalDelta():void
        {
            globalDelta = kMatrix.transpose().multiply(globalForce);
            
            globalDelta.debugPrint();
        }
        
        public function getDelta():IMap
        {
            return null;
        }
        
        public function getForce():IMap
        {
            return null;
        }
        
    }
}